package com.sky.aspect;

import com.sky.annotation.LimitAccess;
import com.sky.exception.BaseException;
import lombok.SneakyThrows;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 限流操作
 */
@Aspect
@Component
public class LimitAccessAspect {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @SneakyThrows
    @Before("execution(* com.sky.controller.admin.*.*(..))&& @annotation(com.sky.annotation.LimitAccess)")
    public void limitAccess(JoinPoint jp) {
        // 拿到目标方法签名
        MethodSignature ms = (MethodSignature) jp.getSignature();
        // 拿到方法上指定的注解
        LimitAccess annotation = ms.getMethod().getAnnotation(LimitAccess.class);
        // 拿到注解上面的值：次数和时间
        long count = annotation.count();
        long time = annotation.time();

        // 限流的redisKey
        // 限流的思路就是拿方法上面的注解,上面规定了时间和次数,用redis来存储限流的数据,以限流的方法设置一个键,方法上面的time作为键的生存时间
        // 用户访问一次,键的值就加一,直到超过方法上面注解的count,就得到方法对应键的剩余生存时间,抛出异常

        String redisKey = "limit:access" + jp.getTarget().getClass().getName() + ":" + ms.getMethod().getName(); //键
        redisTemplate.opsForValue().setIfAbsent(redisKey, 0, time, TimeUnit.SECONDS); //值

        Integer value = (Integer) redisTemplate.opsForValue().get(redisKey);
        if (value != null) {
            // 当访问次数大于限流的次数
            if (value >= count) {
                // 剩余时间
                Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
                throw new BaseException("接口访问次数过多,请" + expire + "秒再试");
            }
            redisTemplate.opsForValue().set(redisKey, value + 1, 0);
        }
    }
}
